﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using System.Collections;

namespace PJ.Data
{
    /// <summary>
    /// Abstract implementation class of <see cref="IObjectDataCache"/>.
    /// </summary>
    public abstract class ObjectDataCache<TIObjectData> : IObjectDataCache<TIObjectData>
        where TIObjectData : class, IObjectData
    {
        #region Constructors

        /// <summary>
        /// Initialise a new instance of <see cref="ObjectDataCache"/>.
        /// </summary>
        /// <param name="cache"><see cref="ICacheManager"/> cache manager instance.</param>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="cache"/> is null.</exception>
        public ObjectDataCache(ICacheManager cache)
        {
            if (cache == null)
                throw new ArgumentNullException();
            _Cache = cache;
        }
        #endregion

        #region Public Interface implementation


        /// <summary>
        /// Get a collection of objects from the cache
        /// </summary>
        /// <param name="globalIds">List of ids of the objects to find</param>
        /// <param name="notFound">List of ids of the objects not found in cache</param>
        /// <returns>Dictionary of items found in cache or null if no items found</returns>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="globalId"/> is <see langword="null" /> or empty.</exception>
        public Dictionary<string, TIObjectData> Find(string[] globalIds, out string[] notFound)
        {
            if (globalIds == null || globalIds.Count() == 0)
                throw new ArgumentNullException("globalId");
            Dictionary<string, TIObjectData> items = new Dictionary<string, TIObjectData>();
            List<string> notFoundList = new List<string>();
            foreach (string globalId in globalIds)
            {
                TIObjectData data = Find(globalId);
                if (data == null)
                    notFoundList.Add(globalId);
                else
                    items.Add(globalId, data);
            }
            if (notFoundList.Count == 0)
                notFound = null;
            else
                notFound = notFoundList.ToArray();
            if (items.Count == 0)
                return null;
            else
                return items;
        }
        
        /// <summary>
        /// Finds the <see cref="IObjectData"/> instance in the cache.
        /// </summary>
        /// <param name="globalId">Object global unique identifier</param>
        /// <returns>A <see cref="IObjectData"/>instance object if found or returns null</returns>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="globalId"/> is null or empty.</exception>
        public TIObjectData Find(string globalId)
        {
            if (globalId == null || globalId.Length == 0)
                throw new ArgumentNullException("globalId");
            return (TIObjectData)_Cache.GetData(globalId);
        }


        /// <summary>
        /// Adds the <see cref="IObjectData"/> instance to the cache.
        /// </summary>
        /// <param name="instance">Instance to add to the cache</param>
        /// <returns><paramref name="instance"/> value<see langword="null" /></returns>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="instance"/> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentException">If the passed <paramref name="instance"/> GlobalID or one of the child class key properties (Id for <see cref="IEntityData"/>, Name for <see cref="IItemData"/>, etc) are <see langword="null" /> or empty.</exception>
        public virtual TIObjectData Add(TIObjectData instance)
        {
            if (instance == null)
                throw new ArgumentNullException();
            if (instance.GlobalId == null || instance.GlobalId == "")
                throw new ArgumentException("GlobalId is null or empty", "instance");
            if (!instance.CachedDate.HasValue)
                instance.CachedDate = DateTime.Now;
            _Cache.Add(instance.GlobalId, instance);
            return instance;
        }

        #endregion

        #region Interface override
        /// <summary>
        /// Finds the <see cref="IObjectData"/> instance in the cache.
        /// </summary>
        /// <param name="globalId">Object global unique identifier</param>
        /// <returns>A <see cref="IObjectData"/>instance object if found or returns null</returns>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="globalId"/> is null or empty.</exception>
        IObjectData IObjectDataCache.Find(string globalId)
        {
            return this.Find(globalId);
        }

        /// <summary>
        /// Adds the <see cref="IObjectData"/> instance to the cache.
        /// </summary>
        /// <param name="instance">Object to add to the cache</param>
        /// <returns><paramref name="instance"/> value<see langword="null" /></returns>
        /// <exception cref="ArgumentNullException">If the passed <paramref name="instance"/> is null.</exception>
        IObjectData IObjectDataCache.Add(IObjectData instance)
        {
            return this.Add((TIObjectData)instance);
        }

        #endregion

        #region Protected Methods
        /// <summary>
        /// Finds the data object in the cache using a cached key.
        /// </summary>
        /// <param name="keyName">Name of the key</param>
        /// <param name="keyValue">Key index value</param>
        /// <returns>The data object if found or returns null</returns>
        protected virtual TIObjectData FindByKey(string keyName, string keyValue)
        {
            if (keyName == null || keyName == "")
                throw new ArgumentNullException("keyName");
            if (keyValue == null || keyValue == "")
                throw new ArgumentNullException("keyValue");

            // Find the GlobalId value attached to the key
            string globalId = _Cache.GetData(typeof(TIObjectData).Name + '.' + keyName + '@' + keyValue) as string;
            if (globalId == null || globalId == "")
                return null;
            else
                return Find(globalId); // Find the IObjectData instance attached to the key
        }
        /// <summary>
        /// Set : Adds a key and the instance to the cache.
        /// </summary>
        /// <param name="globalId">Global ID of the object to key</param>
        /// <param name="keyName">Name of the key to add the cache</param>
        /// <param name="keyValue">Key index value</param>
        protected void AddKey(string globalId, string keyName, string keyValue)
        {
            if (globalId == null || globalId == "")
                throw new ArgumentNullException("globalId");
            if (keyName == null || keyName == "")
                throw new ArgumentNullException("keyName");
            if (keyValue == null || keyValue == "")
                throw new ArgumentNullException("keyValue");

            _Cache.Add(typeof(TIObjectData).Name + '.' + keyName + '@' + keyValue, globalId);
        }
        /// <summary>
        /// Adds an object global ids collection to the cache.
        /// </summary>
        /// <param name="keyName">Name of the key to add the cache</param>
        /// <param name="keyValue">Key index value</param>
        /// <param name="collection">Object global ids collection to add to the cache</param>
        protected void AddCollection(string keyName, string keyValue, string[] collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");
            if (keyName == null || keyName == "")
                throw new ArgumentNullException("keyName");
            if (keyValue == null || keyValue == "")
                throw new ArgumentNullException("keyValue");
            _Cache.Add(typeof(TIObjectData).Name + '.' + keyName + '@' + keyValue, collection);
        }
        /// <summary>
        /// Gets an object global ids collection from the cache.
        /// </summary>
        /// <param name="keyName">Name of the key to add the cache</param>
        /// <param name="keyValue">Key index value</param>
        protected string[] FindCollection(string keyName, string keyValue)
        {
            if (keyName == null || keyName == "")
                throw new ArgumentNullException("keyName");
            if (keyValue == null || keyValue == "")
                throw new ArgumentNullException("keyValue");
            return (string[])_Cache.GetData(typeof(TIObjectData).Name + '.' + keyName + '@' + keyValue);
        }

        #endregion

        #region Fields
        protected readonly ICacheManager _Cache;
        #endregion

    }
}
